﻿using System;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Net;
using System.Net.Sockets;
using System.Text;
using SocketChat.Core.WinForm;
using System.Collections.Generic;
using System.Web.Script.Serialization;

namespace SocketChat.Core.Sockets
{
    public class SocketServer
    {
        private Socket _TcpSocket;
        public BindingList<Client> OnlineClients { get; set; }
        public Dictionary<Guid, Socket> _OnlineSockets { get; set; }
        private Action<Socket> _ReceiveMsgAction;
        private Action _RemoveExpireClient = new Action(() => { });
        /// <summary>
        /// 客户端Socket过期时间
        /// </summary>
        private int _ExpireTime = 2;
        public int ExpireTime {
            get { return _ExpireTime; }
            set { _ExpireTime = value; }
        }
        public Guid Id { get; set; }
        #region 用于显示和移除客户端的委托(外部属性注入)
        //private Action<string> _AddClient;
        //public Action<string> AddClient {
        //    get {
        //        return _AddClient ?? new Action<string>((str) => { });
        //    }
        //    set { _AddClient = value; }
        //}

        //private Action<string> _RemoveClient;
        //public Action<string> RemoveClient {
        //    get {
        //        return _RemoveClient ?? new Action<string>((str) => { });
        //    }
        //    set { _RemoveClient = value; }
        //}
        #endregion

        #region 显示消息用到的委托和方法
        private Action<ListBoxItem> _AddMsgAction;
        /// <summary>
        /// 显示消息的委托(属性注入)
        /// </summary>
        public Action<ListBoxItem> AddMsgAction {
            get {
                return _AddMsgAction ?? new Action<ListBoxItem>((item) => { });
            }
            set { _AddMsgAction = value; }
        }
        private void AddMsg(string msg) {
            AddMsg(msg, this.Id);
        }
        private void AddMsg(string msg, Guid id) {
            var name = this.Id == id ? "服务器" : this.OnlineClients.FirstOrDefault(c => c.Id == id).Name;
            AddMsg(msg, name, DrawType.Msg);
        }
        private void AddMsg(string msg, string name, DrawType drawType) {
            AddMsgAction(new ListBoxItem { Time = DateTime.Now, Body = msg, DrawType = drawType, Name = name });
        }
        #endregion

        /// <summary>
        /// 构造函数
        /// </summary>
        public SocketServer() {
            Id = Guid.NewGuid();
            _TcpSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            OnlineClients = new BindingList<Client>();
            OnlineClients.ListChanged += (sender, e) =>
            {
                this.OnlineClients.ToDictionary(c => c.Id).ToList().ForEach(c =>
                {
                    var client = this.OnlineClients.FirstOrDefault(o => o.Id == c.Key);
                    if ((DateTime.Now - client.HeartBeatTime).Seconds > ExpireTime)
                        this.OnlineClients.Remove(client);
                });
            };
            _ReceiveMsgAction = new Action<Socket>(ReceiveMsg);
            _RemoveExpireClient.BeginInvoke(RemoveExpireClientCallBack, null);
        }


        private void RemoveExpireClientCallBack(IAsyncResult ar) {
            _RemoveExpireClient.EndInvoke(ar);
            //var expires = ClientList.Where(c => (DateTime.Now - c.HeartBeatTime).Seconds >= ExpireTime).ToList();
            //foreach (var item in expires) {
            //    ClientList.Remove(item);
            //}
            System.Threading.Thread.Sleep(500);
            _RemoveExpireClient.BeginInvoke(RemoveExpireClientCallBack, null);
        }

        /// <summary>
        /// 绑定和监听
        /// </summary>
        /// <param name="localEP"></param>
        /// <param name="backlog"></param>
        public void BindAndListen(IPEndPoint localEP, int backlog) {
            _TcpSocket.Bind(localEP);
            _TcpSocket.Listen(backlog);

            AddMsg(string.Format("开始监听:{0}......", localEP.ToString()));
            _TcpSocket.BeginAccept(AcceptCallback, null);
        }

        /// <summary>
        /// 接受客户端连接回调
        /// </summary>
        /// <param name="ar"></param>
        private void AcceptCallback(IAsyncResult ar) {
            var client = _TcpSocket.EndAccept(ar);
            _ReceiveMsgAction.BeginInvoke(client, ReceiveMsgCallBack, client);
        }

        /// <summary>
        /// 使用消息标记(换行符)发送消息,处理Tcp消息边界
        /// </summary>
        /// <param name="client">客户端Socket</param>
        /// <param name="msg">消息</param>
        private void Send(Socket client, Msg msg) {
            NetworkStream stream = new NetworkStream(client);
            StreamWriter sw = new StreamWriter(stream, Encoding.UTF8);
            //WriteStr(msg);
            if (client.Connected) {
                sw.WriteLine(msg.ToString());
                sw.Flush();
            }
        }

        /// <summary>
        /// 异步接收消息
        /// </summary>
        private void ReceiveMsg(Socket socket) {
            try {
                if (socket.Connected) {
                    NetworkStream stream = new NetworkStream(socket);
                    StreamReader reader = new StreamReader(stream, Encoding.UTF8);
                    while (!reader.EndOfStream) {
                        //读取消息
                        var msg = Msg.Prase(reader.ReadLine().Trim());
                        if (msg.Header == MsgType.HeartBeat) {
                            HandleHeartBeat(socket, msg);
                        }
                        else if (msg.Header == MsgType.CheckIn) {
                            HandleCheckIn(socket, msg);
                        }
                        else {
                            AddMsg(msg.Body, msg.Id);
                        }
                        //var client = OnlineClients.FirstOrDefault(c => c.Socket == socket);
                        //if (client != null) {
                        //    client.HeartBeatTime = DateTime.Now;//更新心跳时间
                        //    //AddMsg(client.HeartBeatTime.ToString(), msg.Name);
                        //}
                        //else {
                        //    OnlineClients.Add(new Client(socket) { HeartBeatTime = DateTime.Now, Name = msg.Name });
                        //}
                        //if (msg.Header != MsgType.HeartBeat)
                        //    AddMsg(msg.Body, msg.Name);
                        //if (msg.Header == MsgType.HeartBeat)
                        //    HeartBeatRespond(socket);
                    }
                }
            }
            catch (Exception ex) {
                AddMsg(ex.Message, "异常", DrawType.Error);
            }
        }

        /// <summary>
        /// 异步接收消息回调
        /// </summary>
        /// <param name="ar"></param>
        private void ReceiveMsgCallBack(IAsyncResult ar) {
            var client = ar.AsyncState as Socket;
            _ReceiveMsgAction.EndInvoke(ar);
            _ReceiveMsgAction.BeginInvoke(client, ReceiveMsgCallBack, client);
        }

        private void HandleHeartBeat(Socket client, Msg msg) {
            //var client = this.OnlineClients.FirstOrDefault(c => c.Id == msg.Id);
        }

        private void HandleCheckIn(Socket clientSocket, Msg msg) {
            var client = Client.Prase(msg.Body);
            var ipEndPoint = (clientSocket.RemoteEndPoint as IPEndPoint);
            client.IP = ipEndPoint.Address.ToString();
            client.Port = ipEndPoint.Port;
            client.HeartBeatTime = DateTime.Now;
            this.OnlineClients.Add(client);
        }

        /// <summary>
        /// 客户端心跳回应
        /// </summary>
        /// <param name="socket"></param>
        private void HeartBeatRespond(Socket socket) {
            JavaScriptSerializer jss = new JavaScriptSerializer();
            var msg = new Msg {
                Id = this.Id,
                Header = MsgType.HeartBeatRespond,
                Body = jss.Serialize(OnlineClients.Select(c => new { Id = c.Id, Ip = c.IP, Port = c.Port, HeartBeatTime = c.HeartBeatTime, Name = c.Name }))
            };
            Send(socket, msg);
            //AddMsg(msg.ToString());
        }
    }
}
